﻿' ----------------------------------------------------------------------------------
' Microsoft Developer & Platform Evangelism
' 
' Copyright (c) Microsoft Corporation. All rights reserved.
' 
' THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 
' EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES 
' OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE.
' ----------------------------------------------------------------------------------
' The example companies, organizations, products, domain names,
' e-mail addresses, logos, people, places, and events depicted
' herein are fictitious.  No association with any real company,
' organization, product, domain name, email address, logo, person,
' places, or events is intended or should be inferred.
' ----------------------------------------------------------------------------------

Imports System.Globalization
Imports System.IO
Imports System.Net.Browser
Imports Microsoft.Samples.WindowsPhoneCloud.StorageClient.Credentials
Imports Microsoft.Samples.WindowsPhoneCloud.StorageClient.Serializers

Public Class CloudBlobClient
    Implements ICloudBlobClient

    Private ReadOnly sasService As ISharedAccessSignatureServiceClient

    Private containerUri As Uri
    Private cloudBlobUploaderFactory As ICloudBlobFactory
    Private containerExists As Boolean = False

    ''' <summary>
    ''' Initializes a new instance of the CloudBlobClient class to perform operations with Blobs and Containers using Shared Access Signatures.
    ''' </summary>
    ''' <param name="sasService">A valid Shared Access Signature service client instance.</param>
    Public Sub New(ByVal sasService As ISharedAccessSignatureServiceClient)
        If sasService Is Nothing Then
            Throw New ArgumentNullException("The Shared Access Signature service client cannot be null.", "sasService")
        End If

        Me.sasService = sasService
    End Sub

    ''' <summary>
    ''' Initializes a new instance of the CloudBlobClient class to perform operations with Blobs and Containers using the Azure Storage credentials.
    ''' </summary>
    ''' <param name="containerBaseAddress">The Blob service endpoint to use to create the client.</param>
    ''' <param name="credentials">The account credentials.</param>
    Public Sub New(ByVal containerBaseAddress As String, Optional ByVal credentials As IStorageCredentials = Nothing)
        Me.containerUri = New Uri(containerBaseAddress, UriKind.Absolute)
        Me.StorageCredentials = If(credentials, New StorageCredentialsAnonymous())
    End Sub

    Public Property StorageCredentials() As IStorageCredentials

    Friend Property CloudBlobFactory() As ICloudBlobFactory
        Get
            Return If(Me.cloudBlobUploaderFactory IsNot Nothing, Me.cloudBlobUploaderFactory, New CloudBlobFactory())
        End Get

        Set(ByVal value As ICloudBlobFactory)
            Me.cloudBlobUploaderFactory = value
        End Set
    End Property

    Public Sub Upload(ByVal blobName As String, ByVal fileStream As Stream, ByVal callback As Action(Of CloudOperationResponse(Of Boolean))) Implements ICloudBlobClient.Upload
        If (Me.containerUri IsNot Nothing) AndAlso ((Not Me.IsContainerSASExpired())) Then
            Me.InnerUpload(blobName, fileStream, callback)
        Else
            If Me.sasService IsNot Nothing Then
                Me.UploadUsingSasService(blobName, fileStream, callback)
            Else
                Me.CreateContainerIfNotExist(Of Boolean)(Sub() Me.InnerUpload(blobName, fileStream, callback), callback)
            End If
        End If
    End Sub

    Public Sub ListBlobsWithPrefix(ByVal prefix As String, ByVal useFlatBlobListing As Boolean, ByVal callback As Action(Of CloudOperationResponse(Of IEnumerable(Of ICloudBlob)))) Implements ICloudBlobClient.ListBlobsWithPrefix
        If Me.sasService IsNot Nothing Then
            Me.sasService.GetBlobsSharedAccessSignatures(prefix, useFlatBlobListing, callback)
        Else
            Me.CreateContainerIfNotExist(Of IEnumerable(Of ICloudBlob))(Sub() Me.InnerListBlobsWithPrefixUsingStorageKey(prefix, useFlatBlobListing, callback), callback)
        End If
    End Sub

    Private Sub UploadUsingSasService(ByVal blobName As String, ByVal fileStream As Stream, ByVal callback As Action(Of CloudOperationResponse(Of Boolean)))
        Me.sasService.GetContainerSharedAccessSignature(
            Sub(r)
                If r.Exception Is Nothing Then
                    Me.containerUri = r.Response
                    Me.InnerUpload(blobName, fileStream, callback)
                Else
                    callback(New CloudOperationResponse(Of Boolean)(False, r.Exception))
                End If
            End Sub)
    End Sub

    Private Sub CreateContainerIfNotExist(Of T)(ByVal successCallback As Action, ByVal errorCallback As Action(Of CloudOperationResponse(Of T)))
        If Not Me.containerExists Then
            Dim container = Me.CloudBlobFactory.CreateCloudBlobContainer(Me.StorageCredentials)
            container.CreateContainerIfNotExist(Me.containerUri, Sub(e)
                                                                     If e.Exception Is Nothing Then
                                                                         Me.containerExists = True
                                                                         successCallback()
                                                                     Else
                                                                         errorCallback(New CloudOperationResponse(Of T)(Nothing, e.Exception))
                                                                     End If
                                                                 End Sub)
        Else
            successCallback()
        End If
    End Sub

    Private Sub InnerListBlobsWithPrefixUsingStorageKey(ByVal blobPrefix As String, ByVal useFlatBlobListing As Boolean, ByVal callback As Action(Of CloudOperationResponse(Of IEnumerable(Of ICloudBlob))))
        Dim serviceOperation = String.Concat(Me.containerUri, "?restype=container&comp=list&timeout=90")

        If Not useFlatBlobListing Then
            serviceOperation = String.Concat(serviceOperation, "&delimiter=/")
        End If

        If Not String.IsNullOrEmpty(blobPrefix) Then
            serviceOperation = String.Concat(serviceOperation, "&prefix=", blobPrefix.Replace("\", "/"))
        End If

        ' Adding an incremental seed to avoid a cached response.
        serviceOperation = String.Concat(serviceOperation, "&incrementalSeed=", Date.UtcNow.Ticks)

        Dim request = CType(WebRequestCreator.ClientHttp.Create(New Uri(serviceOperation)), HttpWebRequest)
        request.Headers("x-ms-version") = "2009-09-19"
        request.Method = "GET"

        Me.StorageCredentials.SignRequest(request, -1)
        request.BeginGetResponse(
            Sub(ar)
                Try
                    If request.HaveResponse Then
                        Dim response = CType(request.EndGetResponse(ar), HttpWebResponse)
                        Dim serializer = New StorageAccountBlobDataContractSerializer()
                        Dim results = serializer.ReadObject(response.GetResponseStream())

                        callback(New CloudOperationResponse(Of IEnumerable(Of ICloudBlob))(If(results IsNot Nothing, results, New ICloudBlob() {}), Nothing))
                    End If
                Catch exception As Exception
                    callback(New CloudOperationResponse(Of IEnumerable(Of ICloudBlob))(Nothing, If(StorageClientExceptionParser.ParseXmlWebException(TryCast(exception, WebException)), exception)))
                End Try
            End Sub, Nothing)
    End Sub

    Private Sub InnerUpload(ByVal blobName As String, ByVal fileStream As Stream, ByVal callback As Action(Of CloudOperationResponse(Of Boolean)))
        Dim uriBuilder = New UriBuilder(Me.containerUri)
        uriBuilder.Path &= String.Format(CultureInfo.InvariantCulture, "/{0}", blobName.TrimStart("/"c, "\"c))

        Dim uploadUrl = uriBuilder.Uri.AbsoluteUri
        Dim uploader = Me.CloudBlobFactory.CreateCloudBlobUploader(fileStream, uploadUrl, Me.StorageCredentials)

        AddHandler uploader.UploadFinished, Sub(s, r) callback(r)
        uploader.StartUpload()
    End Sub

    Private Function IsContainerSASExpired() As Boolean
        If Me.containerUri Is Nothing Then
            Return True
        End If

        Dim se = Credentials.HttpUtility.ParseQueryString(Me.containerUri.Query)("se")
        If String.IsNullOrEmpty(se) Then
            Return True
        End If

        Dim expirationDate As Date
        If Not Date.TryParseExact(se, "yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal, expirationDate) Then
            Return True
        End If

        Return expirationDate <= Date.UtcNow
    End Function
End Class